package squareBorderPackage;

import be.kuleuven.cs.som.annotate.*;

/**
 * A class of borders involving two squares.
 * 
 * @invar 	The squares of each border must be proper squares.
 * 			|hasProperSquares()
 * @author	Esther Alberts and Jessa Bekker
 * 
 */
public abstract class Border {

	/**
	 * Initialize this border with the given squares and directionFirstSquare.
	 * 
	 * @param 	squares
	 * 			The new squares to which this border must be attached.
	 * @param 	directionOfFirstSquare
	 * 			The new directionOfFirstSquare of this border
	 * @effect 	..
	 * 			|attachSquares(squares, directionOfFirstSquare);
	 */
	public Border(Square[] squares, Direction directionOfFirstSquare) {
		attachSquares(squares, directionOfFirstSquare);
	}
	
	public Border(Square square, Direction direction) {
		Square[] squares = new Square[2];
		squares[0] = square;
		attachSquares(squares, direction);
	}

	public abstract boolean getSlippery();

	public abstract boolean isObstacle();

   /**
	* Terminate this border.
	*
	* @post 	This border is terminated.
	* 			| new.isTerminated()
	* @effect	Each square associated with this border is removed as square of this border, and this border is removed as the border of each square.
	*/
	public void terminate() {
		if (!isTerminated()) {
			this.isTerminated = true;
			if(getSquares()[0] != null && !getSquares()[0].getBorders().containsKey(directionFirstSquare))
				getSquares()[0].removeABorder(directionFirstSquare);
			if(getSquares()[1] != null && !getSquares()[1].getBorders().containsKey(directionFirstSquare))
				getSquares()[1].removeABorder(directionFirstSquare.getOpposite());
		}
	}

	/**
	 * Check whether this border is terminated.
	 */
	@Basic @Raw
	public boolean isTerminated() {
		return this.isTerminated;
	}

	/**
	 * Variable registering whether this border is terminated.
	 */
	private boolean isTerminated;

	/**
	 * Return the squares to which this border is attached.
	 */
	// @Basic @Raw
	public Square[] getSquares() {
		return squares.clone();
	}

	/**
	 * Return the square which has in the given direction this border.
	 * 
	 * @param	direction
	 * 			The direction in which the square references this border.
	 * @throws	IllegalArgumentException("This border has no square which has this border in the given direction!")
	 * 			|!(getSquares()[0].getBorders().get(direction)==this || getSquares()[1].getBorders().get(direction)==this)
	 */
	public Square getSquare(Direction direction) throws IllegalArgumentException {
		if (getSquares()[0].getBorders().get(direction) == this)
			return getSquares()[0];
		if (getSquares()[1].getBorders().get(direction) == this)
			return getSquares()[1];
		else
			throw new IllegalArgumentException(
					"This border has no square which has this border in the given direction!");
	}

	/**
	 * Check whether the given square is attached to this border.
	 * 
	 * @param square
	 *            The square to be checked.
	 * @return Return true if and only if the given square is attached to this
	 *         border. |result == (getSquares()[0].equals(square) ||
	 *         getSquares()[1].equals(square)
	 */
	public boolean hasAsSquare(Square square) {
		return (getSquares()[0].equals(square) || getSquares()[1].equals(square));
	}

	/**
	 * Check whether this border can have the given squares as its squares.
	 * 
	 * @param	squares
	 * 			The squares to check.
	 * @return	False if the array does not contains exactly 2 squares.
	 * 			| if(squares.length != 2)
	 * 			|	then ( result == false) 
	 * @return	if this border references two effective squares, return false if these two squares reference 
	 * 			this border not in opposite directions
	 * 			|if(!(squares[0]==null && squares[1]==null))
	 * 			|	for each direction:
	 * 			|		if(getSquares()[0].getBorders().get(direction) == this || 
	 *			|		getSquares()[1].getBorders().get(direction.getOpposite()) != this)
	 *			|		result == false;
	 * @return 	If this border is terminated, true if and only if the given squares are both not effective,
	 * 			otherwise, true if and only if the squares are not both null and if each effective square is not terminated.
	 * 			| if (isTerminated())
	 * 			| then result == (squares[0]==null && squares[1]==null)
	 * 			| else result ==
	 * 			| (!(squares[0]==null && squares[1]==null)
	 *			|	&& ( (squares[0]==null || ! squares[0].isTerminated())
	 *			|			&&(squares[1]==null || ! squares[1].isTerminated()) ) )
	 * @return	Return false if the given squares are equal while they are both effective.
	 */
	@Raw
	public boolean canHaveAsSquares(Square[] squares){
		if(squares.length != 2)
			return false;
		if(this.isTerminated()){
			return (squares[0]==null && squares[1]==null);
		}
		if(!(squares[0]==null || squares[1]==null)){
			for(Direction direction: Direction.getListOfDirections()){
				if(squares[0].getBorders().get(direction) == this && 
						squares[1].getBorders().get(direction.getOpposite()) != this)
					return false;
			}
		}
		return ( !(squares[0]==null && squares[1]==null)
				&& !squares[0].equals(squares[1])
				&& ( (squares[0]==null || ! squares[0].isTerminated())
						&&(squares[1]==null || ! squares[1].isTerminated()) ) );
	}

	/**
	 * Check whether this border has proper squares attached to it.
	 * 
	 * @return	True if and only if 
	 * 				this border can have this squares as his squares
	 * 			and if
	 * 				this border does not reference an effective square or if the square 
	 * 				referenced by this border in turn references this border as the border to which it is attached.
	 * 			|result == 
	 * 			|	canHaveAsSquares(getSquares()) &&
	 * 			|	(getSquares()[0]==null || getSquares()[0].getBorders().containsValue(this)) &&
	 *			|	(getSquares()[1]==null || getSquares()[1].getBorders().containsValue(this))
	 */
	public boolean hasProperSquares(){
		return 	canHaveAsSquares(getSquares()) &&
				(getSquares()[0]==null || getSquares()[0].getBorders().containsValue(this)) &&
				(getSquares()[1]==null || getSquares()[1].getBorders().containsValue(this));
	}

	/**
	 *  Remove a square of this border.
	 * 
	 * @param	square
	 * 			The square to remove from this border.
	 * @throws	IllegalArgumentException("This square is not tangent to this border!")
	 * 			|!(getSquares()[0].equals(square)||getSquares()[1].equals(square))
	 * @post	The given square is removed from this border.
	 * @effect	If the given square is attached to this border, and the other square referenced by this border 
	 * 			not effective, then this border must be terminated.
	 */
	public void removeASquare(Square square) throws IllegalArgumentException{
		if((getSquares()[0].equals(square) && getSquares()[1].equals(null)) || 
				(getSquares()[1].equals(square) && getSquares()[0].equals(null)))
			this.terminate();
		else{
			if(getSquares()[0].equals(square)){
				squares[0]=null;
			}
			else if(getSquares()[1].equals(square)){
				squares[1]=null;
			}
			else
				throw new IllegalArgumentException("This square is not tangent to this border!");
		}
	}

	/**
	 * Set the squares of this border.
	 * 
	 * @param 	squares 
	 * 			the squares to set
	 * @post	The new squares are equal to the given squares.
	 * 			|new.getSquares() = squares
	 * @throws	IllegalArgumentException()
	 * 			|!canHaveAsSquares(squares)
	 */
	private void setSquares(Square[] squares) throws IllegalArgumentException {
		if(!canHaveAsSquares(squares))
			throw new IllegalArgumentException("These squares are not valid squares");
		this.squares = squares;
	}

	/**
	 * Attach the given squares to this border.
	 * 
	 * @param	squares
	 * 			The squares to which this border must be attached.
	 * @param	directionOfFirstSquare
	 * 			the direction in which squares[0] must reference this border.
	 * @post	The given squares are attached to this border
	 * @post	This border is attached to the given squares
	 * @post	If this border was attached to some square, 
	 * 			or in the oppositeDirection of the given direction,
	 * 			that square no longer references this border.
	 * @throws	IllegalStateException
	 * 			This border is already terminated
	 * 			|this.isTerminated()
	 * @throws	IllegalArgumentException
	 * 			|!canHaveAsSquares(squares)
	 */
	public void attachSquares(Square[] squares, Direction directionOfFirstSquare)
			throws IllegalStateException, IllegalArgumentException {
		if (this.isTerminated())
			throw new IllegalStateException("Terminated border");
		if (!this.canHaveAsSquares(squares))
			throw new IllegalArgumentException("Not valid squares");
		this.setDirectionFirstSquare(directionOfFirstSquare);
		if(getSquares()[0] != null)
			this.getSquares()[0].removeABorder(directionOfFirstSquare);
		if(getSquares()[1] != null)
			this.getSquares()[1].removeABorder(directionOfFirstSquare.getOpposite());
		setSquares(squares);
		if(squares[0] != null)
			squares[0].setBorderAt(directionOfFirstSquare, this);
		if(squares[1] != null)
			squares[1].setBorderAt(directionOfFirstSquare.getOpposite(), this);
		if(!this.isObstacle() && squares[0] != null && squares[1] != null)
			squares[0].mergeWith(squares[1]);
	}

	/**
	 * Variable referencing the squares to which this border is tangent.
	 */
	private Square squares[] = new Square[2];

	/**
	 * Return the direction in which squares[0] references this border.
	 */
	public Direction getDirectionFirstSquare() {
		return directionFirstSquare;
	}
	
	/**
	 * Set the direction in which squares[0] references this border.
	 * 
	 * @param 	direction
	 * 			The direction to set this variable to.
	 */
	private void setDirectionFirstSquare(Direction direction) {
		this.directionFirstSquare = direction;
	}
	
	/**
	 * Variable referencing the direction in which squares[0] references this border.
	 */
	private Direction directionFirstSquare;

	
	/**
	 * Return the strongest border of this border and the given border.
	 * 
	 * @param	other
	 * 			The border to compare this border with
	 */
	public abstract Border getStrongestBorder(Border other);
	

}
